#ifndef _XLCD_TOUCH
#define _XLCD_TOUCH

#include <XPT2046_Touchscreen.h>
#include <Arduino.h>
#include <TFT_eSPI.h>
#include "screen.h"
#include "filesystem.h"
#include "../logger.h"

#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

struct XTouchPanelConfig
{
    float xCalM;
    float yCalM;
    float xCalC;
    float yCalC;
};


SPIClass x_touch_spi = SPIClass(HSPI);
XPT2046_Touchscreen x_touch_touchScreen(XPT2046_CS, XPT2046_IRQ);
XTouchPanelConfig x_touch_touchConfig;


const char *xtouch_paths_touch = "/touch.json";
bool cal_loaded = false;
#define XTOUCH_FS SPIFFS


class ScreenPoint
{
public:
    int16_t x;
    int16_t y;

    // default constructor
    ScreenPoint()
    {
    }

    ScreenPoint(int16_t xIn, int16_t yIn)
    {
        x = xIn;
        y = yIn;
    }
};

ScreenPoint getScreenCoords(int16_t x, int16_t y)
{
    if (cal_loaded){
        int16_t xCoord = round((x * x_touch_touchConfig.xCalM) + x_touch_touchConfig.xCalC);
        int16_t yCoord = round((y * x_touch_touchConfig.yCalM) + x_touch_touchConfig.yCalC);
        if (xCoord < 0)
            xCoord = 0;
        if (xCoord >= 320)
            xCoord = 320 - 1;
        if (yCoord < 0)
            yCoord = 0;
        if (yCoord >= 240)
            yCoord = 240 - 1;
        return (ScreenPoint(xCoord, yCoord));
    }
    else{
        int16_t xCoord = x;
        int16_t yCoord = y;
        return (ScreenPoint(xCoord, yCoord));
    }
}

void xtouch_loadTouchConfig(XTouchPanelConfig &config)
{
    // Open file for reading
    fs::File file = xtouch_filesystem_open(XTOUCH_FS, xtouch_paths_touch);

    // Allocate a temporary JsonDocument
    // Don't forget to change the capacity to match your requirements.
    // Use arduinojson.org/v6/assistant to compute the capacity.
    StaticJsonDocument<512> doc;

    // Deserialize the JSON document
    DeserializationError error = deserializeJson(doc, file);
    if (error)
        logln("[Touch] Failed to read touch config");

    config.xCalM = doc["xCalM"].as<float>();
    config.yCalM = doc["yCalM"].as<float>();
    config.xCalC = doc["xCalC"].as<float>();
    config.yCalC = doc["yCalC"].as<float>();

    file.close();
}

void xtouch_saveTouchConfig(XTouchPanelConfig &config)
{
    StaticJsonDocument<512> doc;
    doc["xCalM"] = config.xCalM;
    doc["yCalM"] = config.yCalM;
    doc["xCalC"] = config.xCalC;
    doc["yCalC"] = config.yCalC;
    xtouch_filesystem_writeJson(XTOUCH_FS, xtouch_paths_touch, doc);
}

void xtouch_resetTouchConfig()
{
    logln("[TOUCH] Resetting TouchConfig");
    xtouch_filesystem_deleteFile(XTOUCH_FS, xtouch_paths_touch);
    delay(500);
    ESP.restart();
}

bool hasTouchConfig()
{
    logf("[TOUCH] Checking for TouchConfig \"%s\"", xtouch_paths_touch);
    return xtouch_filesystem_exist(XTOUCH_FS, xtouch_paths_touch);
}

void xtouch_screen_touchRead(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
    if (x_touch_touchScreen.tirqTouched() && x_touch_touchScreen.touched())
    {
        ScreenPoint sp = ScreenPoint();
        TS_Point p = x_touch_touchScreen.getPoint();
        sp = getScreenCoords(p.x, p.y);
        data->state = LV_INDEV_STATE_PR;
        data->point.x = sp.x;
        data->point.y = sp.y;
        // Serial.printf("[XTouch][TOUCH] Touch: %d, %d\n", data->point.x, data->point.y);
    }
    else
    {
        data->state = LV_INDEV_STATE_REL;
    }
}

void touch_init(){
    logln("[TOUCH] START INIT.");
    x_touch_spi.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
    x_touch_touchScreen.begin(x_touch_spi);
    /*Initialize the (dummy) input device driver*/
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = xtouch_screen_touchRead;
    lv_indev_drv_register(&indev_drv);
}

// 阻塞的：进入触摸校准的界面中
void touch_cal_setup(bool forceSetup = false)
{
    if (hasTouchConfig() && !forceSetup)
    {
        logf("[TOUCH] TouchConfig \"%s\" Loaded from disk", xtouch_paths_touch);
        xtouch_loadTouchConfig(x_touch_touchConfig);
        cal_loaded = true;
    }
    else
    {
        logln("[TOUCH] Touch Setup");
        TS_Point p;
        int16_t x1, y1, x2, y2;
        tft.fillScreen(ILI9341_BLACK);
        // wait for no touch
        tft.setCursor(20, 200);
        tft.setTextColor(TFT_WHITE, TFT_BLACK, true);
        tft.setTextSize(1);
        tft.fillRect(20, 200, 240, 30, TFT_BLACK); 
        tft.printf("touch panel Cal ...");
        while (x_touch_touchScreen.touched())
            ;
        tft.drawFastHLine(0, 10, 20, ILI9341_WHITE);
        tft.drawFastVLine(10, 0, 20, ILI9341_WHITE);
        while (!x_touch_touchScreen.touched())
            ;
        delay(50);
        p = x_touch_touchScreen.getPoint();
        x1 = p.x;
        y1 = p.y;
        tft.drawFastHLine(0, 10, 20, ILI9341_BLACK);
        tft.drawFastVLine(10, 0, 20, ILI9341_BLACK);
        delay(500);

        while (x_touch_touchScreen.touched())
            ;
        tft.drawFastHLine(300, 230, 20, ILI9341_WHITE);
        tft.drawFastVLine(310, 220, 20, ILI9341_WHITE);

        while (!x_touch_touchScreen.touched())
            ;
        delay(50);
        p = x_touch_touchScreen.getPoint();
        x2 = p.x;
        y2 = p.y;
        tft.drawFastHLine(300, 230, 20, ILI9341_BLACK);
        tft.drawFastVLine(310, 220, 20, ILI9341_BLACK);

        int16_t xDist = 320 - 40;
        int16_t yDist = 240 - 40;

        x_touch_touchConfig.xCalM = (float)xDist / (float)(x2 - x1);
        x_touch_touchConfig.xCalC = 20.0 - ((float)x1 * x_touch_touchConfig.xCalM);
        // y
        x_touch_touchConfig.yCalM = (float)yDist / (float)(y2 - y1);
        x_touch_touchConfig.yCalC = 20.0 - ((float)y1 * x_touch_touchConfig.yCalM);
        xtouch_saveTouchConfig(x_touch_touchConfig);
        cal_loaded = true;
    }
}

#endif